package chapter2;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author caiya
 * @date 2020/3/15 22:49
 */
@Slf4j
public class AtomicStampedReferenceTest {
    public static void main(String[] args) {
        /**
         * AtomicInteger 类底层使用了CAS操作，但是不能解决CAS的ABA问题，要解决ABA问题，只需要每次操作的时候，添加一个version字段，进行version递增即可，这样
         * 每次expect的时候就会去判断当前版本，如果版本不一致就放弃修改，线程就会继续进行自旋尝试。
         *
         * AtomicStampedReference 使用stamp类似于version字段来解决ABA问题
         */
        AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(1, 0);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("线程：{}, 初始值：{}", Thread.currentThread().getName(), stampedReference.getReference());
                int stamp = stampedReference.getStamp();
                try {
                    // 休眠1s，以便线程2可以在休眠期间使用CAS进行ABA的操作
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 正常情况下这里应该为 false，因为这里的1已经不是线程1最原始的1了，而是被线程2进行CAS操作后的1
                boolean b = stampedReference.compareAndSet(1, 2, stamp, stamp + 1);
                log.debug("线程：{}，cas 结果：{}", Thread.currentThread().getName(), b);
            }
        }, "线程1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 确保线程1先执行（实际线程1也可能先执行）
                Thread.yield();
                int stamp = stampedReference.getStamp();
                stampedReference.compareAndSet(1, 2, stamp, stamp + 1);
                log.debug("线程：{}，【increment】后：{}", Thread.currentThread().getName(), stampedReference.getReference());
                stampedReference.compareAndSet(2, 1, stamp, stamp + 1);
                log.debug("线程：{}，【decrement】后：{}", Thread.currentThread().getName(), stampedReference.getReference());
            }
        }, "线程2");

        thread1.start();
        thread2.start();
    }
}
